-module(edrp_proxy).

-export([init_proxy/0,
         free_proxy/0,
         proxy/3,
         any_to_binary/1,
         start/0,
         start/1,
         filter1/1,
         filter2/1
        ]).

-include("edrp_server.hrl").
-include("edrp_http.hrl").

-record(state, {csock  = undefined, 
                ssock  = undefined,
                saddr  = #addr{}, 
                kalive = true,
                status = true, 
                sresp  = false,
                inf    = [],
                outf   = []
               }).

%%==========================================================================
%% API
%%==========================================================================

init_proxy() ->
    ok.
    %% edrp_cache:init_cache().

free_proxy() ->
    ok.
    %% edrp_cache:free_cache().

proxy(Inf, Outf, Socket) ->
    case inet:setopts(Socket, [{packet, http}, {active, once}, 
                               {send_timeout, ?IDLE_TIMEOUT}]) of
        ok ->
            try 
                wait_request(#state{csock=Socket, inf=Inf, outf=Outf})
            catch
                {stop, _Reason, State} ->
                    close(State)
            end;
        {error, Reason} ->
            {error, Reason}
    end.

%%==========================================================================
%% Internal functions
%%==========================================================================

wait_request(State) ->
    receive
        {http_request, Socket, Method, Path, Version} 
            when Socket == State#state.csock ->
            Request = #request{method=Method, path=get_path(Path), 
                                              version=Version},
            recv_request(State, Request);
        Other ->
            throw({stop, Other, State})
    after
        ?IDLE_TIMEOUT ->
            throw({stop, timeout, State})
    end.

wait_response(State) ->
    receive
        {http_response, Socket, Version, Status, Message}
            when Socket == State#state.ssock ->
            Response = #response{version=Version, status=Status, 
                                 message=Message},
            recv_response(State, Response);
        {tcp, Socket, Bin} when Socket == State#state.ssock ->
            send_response(State, Bin);
        Other ->
            throw({stop, Other, State#state{status=false}})
    after
        ?IDLE_TIMEOUT ->
            throw({stop, timeout, State#state{status=false}})
    end.

wait(State) ->
    receive
        {http_request, Socket, Method, Path, Version}
            when Socket == State#state.csock ->
            Request = #request{method=Method, path=get_path(Path), 
                               version=Version},
            recv_request(State, Request);
        {http_response, Socket, Version, Status, Message}
            when Socket == State#state.ssock ->
            Response = #response{version=Version, status=Status, 
                                 message=Message},
            recv_response(State, Response);
        {tcp, Socket, Bin} when Socket == State#state.ssock ->
            send_response(State, Bin);
        {tcp_closed, Socket} when Socket == State#state.ssock, 
                                  State#state.sresp == false ->
            throw({stop,over, State#state{status=false}});
        Other ->
            throw({stop, Other, State})
    after
        ?IDLE_TIMEOUT ->
            case State#state.sresp of
                true -> throw({stop, timeout, State});
                false -> throw({stop, timeout, State#state{status=false}})
            end
    end.

close(State) ->
    case State#state.csock of           % close client socket
        undefined -> ok;
        CSock -> gen_tcp:close(CSock)
    end,
    case State#state.ssock of           % close client socket
        undefined -> ok;
        SSock -> gen_tcp:close(SSock)
    end,
    case State#state.saddr of           % return server to dispatcher 
        #addr{ip=undefined, port=undefined} -> ok;
        Addr -> edrp_dispatcher:return_server(Addr, State#state.status)
    end.

recv_request(State = #state{csock=Socket}, Request) ->
    case recv(request, Socket, Request) of
        {ok, NewRequest=#request{version=Version}, Connection} ->
            KeepAlive = keep_alive(Version, Connection),
            handle_request(State#state{kalive=KeepAlive}, NewRequest);
        {error, Reason} ->
            throw({stop, Reason, State})
    end.
    
recv_response(State = #state{ssock=Socket}, Response) ->
    case recv(response, Socket, Response) of
        {ok, NewResponse} ->
            handle_response(State, NewResponse);
        {error, Reason} ->
            throw({stop, Reason, State#state{status=false}})
    end.
    
handle_request(State, Request) ->
    case filter(State#state.inf, Request) of
        {ok, NewRequest} -> send_request(State, NewRequest);
        {stop, Response} -> send_response(State, Response);
        {error, Reason} -> throw({stop, Reason, State})
    end.
    
handle_response(State, Response) ->
    case filter(State#state.outf, Response) of
        {ok, NewResponse} -> send_response(State, NewResponse);
        {stop, Response} -> throw({stop, filter_stop, State});
        {error, Reason} -> throw({stop, Reason, State})
    end.
    
send_request(State=#state{kalive=KAlive}, Request=#request{path=Path}) ->
    case connect(State#state.ssock, Path, State#state.saddr) of
        {ok, Socket, Addr} ->
            case send(request, Socket, Request) of
                ok ->
                    case KAlive of
                        true ->
                            wait(State#state{ssock=Socket,saddr=Addr,
                                             sresp=false});
                        false ->
                            wait_response(State#state{ssock=Socket,
                                                      saddr=Addr,
                                                      sresp=false})
                    end;
                {error, Reason} ->
                    throw({stop,Reason,State#state{ssock=Socket,saddr=Addr,
                                                   status=false}})
            end;        
        {error, no_server} ->
            throw({stop, no_server, State});
        {error, Reason, Addr} ->
            throw({stop, Reason, State#state{saddr=Addr, status=false}})
    end.

send_response(State = #state{csock=Socket, kalive=KAlive}, Response) ->
    case send(response, Socket, Response) of
        ok -> 
            case KAlive of
                true -> wait(State#state{sresp=true});
                false -> throw({stop, over, State})
            end;
        {error, Reason} -> 
            throw({stop, Reason, State})
    end.

recv(request, Socket, Request = #request{method=Method}) ->
    case recv(Socket, Method) of
        {ok, Headers, Body, Connection} ->
            {ok, Request#request{headers=Headers, body=Body}, Connection};
        {error, Reason} ->
            {error, Reason}
    end;
recv(response, Socket, Response) ->
    case recv(Socket, 'POST') of
        {ok, Headers, Body, _Connection} ->
            {ok, Response#response{headers=Headers, body=Body}};
        {error, Reason} ->
            {error, Reason}
    end.

recv(Socket, Method) ->
    case get_headers(Socket) of
        {ok, Headers, Length, Connection} ->
            case Method of
                'POST' ->
                    case get_body(Socket, Length) of
                        {ok, Body} -> {ok, Headers, Body, Connection};
                        {error, Reason} -> {error, Reason}
                    end;
                _Other ->
                    {ok, Headers, [], Connection}
            end;
        {error, Reason} ->
            {error, Reason}
    end.    

get_headers(Socket) ->
    case inet:setopts(Socket, [{active, false}]) of
        ok ->
            headers(Socket, [], 0, undefined, 0);
        {error, Reason} ->
            {error, Reason}
    end.

headers(Socket, Headers, Len, Conn, HeaderCount) ->
    case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
        {ok, http_eoh} -> 
            {ok, Headers, Len, Conn};
        {ok, {http_header, _, 'Content-Length', _, Length}} ->
            headers(Socket, [{'Content-Length',Length}|Headers], Length, 
                    Conn, HeaderCount+1);
        {ok, {http_header, _, 'Connection', _, NewConn}} ->
            headers(Socket, [{'Connection',NewConn}|Headers], Len, NewConn, 
                    HeaderCount+1);
        {ok, {http_header, _, Name, _, Value}} ->
            headers(Socket, [{Name,Value}|Headers], Len, Conn, 
                    HeaderCount+1);
        {error, Reason} ->
            {error, Reason}
    end.
    
get_body(_, 0) -> {ok, []};
get_body(Socket, Len) ->
    Length = list_to_integer(Len),
    case inet:setopts(Socket, [{packet, raw}]) of
        ok ->
            case gen_tcp:recv(Socket, Length, ?IDLE_TIMEOUT) of
                {ok, Body} ->
                    case inet:setopts(Socket, [{packet, http}, 
                                               {active, once}]) of
                        ok ->
                            {ok, Body};
                        {error, Reason} ->
                            {error, Reason}
                    end;
                {error, Reason} ->
                    {error, Reason}
            end;
        {error, Reason} ->
            {error, Reason}
    end.

connect(Socket, Path, Addr) ->
    case Socket of
        undefined ->
            % Get the Host and Port with the Path
            case edrp_dispatcher:apply_server(Path) of
                no_server -> {error, no_server};
                NewAddr -> 
                    case gen_tcp:connect(NewAddr#addr.ip,
                                         NewAddr#addr.port, 
                                         [binary, {send_timeout, 
                                                   ?IDLE_TIMEOUT}]) of
                        {ok, NewSocket} ->
                            {ok, NewSocket, NewAddr};
                        {error, Reason} ->
                            {error, Reason, NewAddr}
                    end
            end;
        _ -> 
            {ok, Socket, Addr}
    end.    

send(request, Socket, Request) ->
    send(Socket, any_to_binary(Request));
send(response, Socket, Response) ->
    send(Socket, any_to_binary(Response)).
        
send(Socket, List) ->
    case inet:setopts(Socket, [{packet, raw}]) of
        ok ->
            case gen_tcp:send(Socket, List) of
                ok ->
                     case inet:setopts(Socket, [{packet, http}, 
                                                {active, once}]) of
                        ok -> ok;
                        {error, Reason} -> 
                            ok
                            %{error, {'-_-', Reason}}
                    end;
                {error, Reason} ->
                    {error, Reason}
            end;
        {error, Reason} ->
            {error, Reason}
    end.

get_path({abs_path, Path}) -> Path;
get_path({absoluteURI, http, _Host, _, Path}) -> Path;
get_path({absoluteURI, _Method, _Host, Path}) -> Path;
get_path({scheme, _Scheme, Path}) -> Path.

get_version({0, 9}) -> <<"HTTP/0.9">>;
get_version({1, 0}) -> <<"HTTP/1.0">>;
get_version({1, 1}) -> <<"HTTP/1.1">>.

keep_alive({1,1}, "close")      -> false;
keep_alive({1,1}, "Close")      -> false;
keep_alive({1,1}, _)            -> true;
keep_alive({1,0}, "Keep-Alive") -> true;
keep_alive({1,0}, _)            -> false;
keep_alive({0,9}, _)            -> false.

add_headers([], L) -> L;
add_headers([{Name, Value}|Rest], L) -> 
    add_headers(Rest, [any_to_binary(Name), <<": ">>, any_to_binary(Value), 
                       <<"\r\n">>|L]).

any_to_binary(A) when is_binary(A) -> A;
any_to_binary(A) when is_atom(A) -> list_to_binary(atom_to_list(A));
any_to_binary(A) when is_list(A) -> list_to_binary(A);
any_to_binary(A) when is_integer(A) -> list_to_binary(integer_to_list(A));
any_to_binary(A) when is_record(A, request) ->
    Method  = any_to_binary(A#request.method),
    Path    = any_to_binary(A#request.path),
    Version = get_version(A#request.version),
    Headers = add_headers(A#request.headers, [<<"\r\n">>, A#request.body]),
    any_to_binary([Method, <<" ">>, Path, <<" ">>, Version, <<"\r\n">> |
                   Headers]);
any_to_binary(A) when is_record(A, response) ->
    Version = get_version(A#response.version),
    Status  = any_to_binary(A#response.status),
    Message = any_to_binary(A#response.message),
    Headers = add_headers(A#response.headers, [<<"\r\n">>,A#response.body]),
    any_to_binary([Version, <<" ">>, Status, <<" ">>, Message, <<"\r\n">> | 
                   Headers]). 

filter([], In) -> {ok, In};
filter([{M,F,A}|Rest], In) ->
    case (catch apply(M, F, A ++ [In])) of
        ok -> 
            filter(Rest, In);
        {update, NewIn} ->
            filter(Rest, NewIn);
        {stop, Out} ->
            {stop, Out};
        Other ->
            {error, Other}
    end.

%%==========================================================================
%% Test functions
%%==========================================================================

start() ->
    start(1234).

start(Port) ->
    case gen_tcp:listen(Port, [binary, {packet,raw}, {reuseaddr,true}]) of
        {ok, LSock} -> 
            process_flag(trap_exit, true),
            S = self(),
            spawn_link(fun() -> accept_loop(S, LSock) end),
            wait_accept(S, LSock);
        {error, Reason} -> 
            {listen_error, Reason}
    end.

wait_accept(S, LSock) ->
    receive
        accepted ->
            spawn_link(fun() -> accept_loop(S, LSock) end),
            wait_accept(S, LSock);
        {'EXIT', _From, _Why} ->
            spawn_link(fun() -> accept_loop(S, LSock) end),
            wait_accept(S, LSock)
    end.

accept_loop(Parent, LSock) ->
    case gen_tcp:accept(LSock) of
        {ok, Socket} -> 
            Parent ! accepted,
            proxy([{?MODULE,filter1,[]}], [{?MODULE,filter2,[]}], Socket);
        {error, Reason} -> exit({accept_error, Reason})
    end.

filter1(_In) ->
    %%io:format("In filter 1~n"),
    ok.
filter2(_In) ->
    %%io:format("In filter 2~n"),
    ok.
